/*
 *  levelUpGame - GridObject.cpp
 *  Copyright 2011 W. Reckman. All rights reserved.
 */
#include "LevelUpApp.h"
#include "GridObject.h"

GridObject::GridObject( const LevelUpApp& appRef_, bool movable_ )
: appRef( appRef_ ), game( appRef_.getGame() ), movementSpeed( game.getBasePlayerSpeed() ),
  currentDir( D_STOP ), intendedDir( D_STOP ), blocked( true ),
  collisionMode( CM_BBOX ), movable( movable_ ), collidable( true ), boundToGrid( true )
{}

GridObject::~GridObject()
{}


void GridObject::draw() const {
	switch ( collisionMode ) {
		case CM_BBOX: BoundingBox::draw(); break;
		case CM_CENTER:
			ofPushStyle(); ofFill(); ofSetColor( 255, 255, 127 );
			ofCircle( getPosition().x, getPosition().y, 2 );
			ofPopStyle();
			break;
		case CM_CIRCLE: {
			ofPushStyle(); ofNoFill(); ofSetColor( 255, 255, 127 );
			const ofRectangle& bb = getBBox();
			ofCircle( getPosition().x, getPosition().y, getFakeRadius() );
			ofPopStyle();
			break;
		}
	}
}

GridObject::COLLISION_MODE GridObject::getCollisionMode() const { return collisionMode; }
bool GridObject::isMovable() const { return movable; }
bool GridObject::isCollidable() const { return collidable; }
int GridObject::getMovementSpeed() const { return movementSpeed; }
DIRECTION GridObject::getDirection() const { return currentDir; }
bool GridObject::isBlocked() const { return blocked; }

void GridObject::setCollisionMode( COLLISION_MODE mode_ ) {
	//if ( mode_ == CM_CIRCLE ) { ofLog( OF_LOG_ERROR, "(GridObject.cpp) collission mode BB_CIRCLE is not implemented!" ); return; }
	collisionMode = mode_;
}

void GridObject::setMovable( bool movable_ ) { movable = movable_; }
void GridObject::setCollidable( bool collidable_ ) { collidable = collidable_; }
void GridObject::setMovementSpeed( int speed_ ) { movementSpeed = speed_; }

void GridObject::setDirection( DIRECTION direction_, bool setCurrentDir ) {
//ofLog( OF_LOG_WARNING, "setDir d%i, curr=%s", direction_, setCurrentDir ? "yes" : "no" ); //TEMP
	if ( setCurrentDir ) {
		currentDir = direction_;
	} else {
		intendedDir = direction_;
	}
}

//FIXME: merge enums?
void GridObject::setDirection( EventArgsJoystick::DIRECTION direction_, bool setCurrentDir ) {
	if ( setCurrentDir ) {
		currentDir = (DIRECTION)direction_;
	} else {
		intendedDir = (DIRECTION)direction_;
	}
}

void GridObject::setBlocked( bool blocked_ ) { blocked = blocked_; }


/* TODO lane wrapping:
 * - wrap coords (in this function i guess...)
 * - draw two images both half on screen if bbox falls outside screen
 */
bool GridObject::tryMovingTo( const ofPoint& pos_, DIRECTION dir_ ) { return tryMovingTo( pos_.x, pos_.y, dir_ ); }
bool GridObject::tryMovingTo( float x_, float y_, DIRECTION dir_ ) {
	if ( ! movable ) return false;
	
	if ( ! boundToGrid || game.dirCoordsAreOnGrid( ofPoint( x_, y_ ), dir_, 0, 0, true, true ) ) {
		int canvasHeight = appRef.getViewController().getCanvasHeight();
		if ( y_ < 0 ) y_ += canvasHeight;
		if ( y_ > canvasHeight - 1 ) y_ -= canvasHeight;
		setPosition( x_, y_ );
		return true;
	} else {
		return false;
	}
}

//NOTE: test this function regarding circle collisions; rewrite structure to something more readable?
bool GridObject::collidesWith( const GridObject& other_ ) const {
	bool rv = false;
	COLLISION_MODE mcm = collisionMode, ocm = other_.getCollisionMode();
	
	if ( collidable && other_.isCollidable() ) {
		if ( mcm == CM_BBOX && ocm == CM_BBOX ) {
			rv = overlapsWith( other_ );
			
		} else if ( mcm == CM_CENTER && ocm == CM_CENTER ) {
			ofPoint c = getPosition();
			rv = ( c == other_.getPosition() ) ? true : false;
			
		} else if ( mcm == CM_CIRCLE && ocm == CM_CIRCLE ) {
			float r1 = getFakeRadius();
			float r2 = other_.getFakeRadius();
			float dist = pointDist( getPosition(), other_.getPosition() );
			rv = ( dist <= r1 + r2 );
			
		} else if ( ( mcm == CM_CIRCLE && ocm == CM_BBOX ) || ( mcm == CM_BBOX && ocm == CM_CIRCLE ) ) {
			const ofPoint& cPos = ( mcm == CM_CIRCLE ) ? getPosition() : other_.getPosition();
			float cRadius = ( mcm == CM_CIRCLE ) ? getFakeRadius() : other_.getFakeRadius();
			ofRectangle bbox = ( mcm == CM_BBOX ) ? getBBox() : other_.getBBox();
			float scale = ( mcm == CM_BBOX ) ? getCollisionBBoxScale() : other_.getCollisionBBoxScale();
			scaleRectangle( bbox, scale );
			rv = circleIntersectsWith( cPos, cRadius, bbox );
			
		} else if ( ( mcm == CM_CIRCLE && ocm == CM_CENTER ) || ( mcm == CM_CENTER && ocm == CM_CIRCLE ) ) {
			float r = ( mcm == CM_CIRCLE ) ? getFakeRadius() : other_.getFakeRadius();
			float dist = pointDist( getPosition(), other_.getPosition() );
			rv = ( dist <= r );
			
		} else { //for CM_CENTER+CM_BBOX and CM_BBOX+CM_CENTER
			ofPoint p = ( mcm == CM_CENTER ) ? getPosition() : other_.getPosition();
			ofRectangle r = ( mcm == CM_BBOX ) ? getBBox() : other_.getBBox();
			float scale = ( mcm == CM_BBOX ) ? getCollisionBBoxScale() : other_.getCollisionBBoxScale();
			scaleRectangle( r, scale );
			
			rv = r.inside( p );
//ofLog( OF_LOG_WARNING, "CvsBB: %2.0f <= %2.0f <= %2.0f && %2.0f <= %2.0f <= %2.0f ?? %s",
//			r.x, p.x, r.x + r.width, r.y, p.y, r.y + r.height, rv ? "true" : "false" ); //TEMP
		}
	}
	
	return rv;
}


/***********************
 * PROTECTED FUNCTIONS *
 ***********************/

bool GridObject::calculateNewPosForDirection( DIRECTION dir, float elapsedTime, ofPoint& newPos ) const {
	bool posUpdated = true;
	switch ( dir ) {
		case D_RIGHT: newPos.x += movementSpeed * elapsedTime; break;
		case D_UP: newPos.y -= movementSpeed * elapsedTime; break;
		case D_LEFT: newPos.x -= movementSpeed * elapsedTime; break;
		case D_DOWN: newPos.y += movementSpeed * elapsedTime; break;
		case D_STOP: posUpdated = false; break;
	}
	return posUpdated;
}

float GridObject::getFakeRadius() const {
	const ofRectangle& bbox = getBBox();
	return ( ( bbox.width < bbox.height ) ? bbox.width / 2.0f : bbox.height / 2.0f ) * getCollisionBBoxScale();
}
